package me.lwn.auth.application;

import me.lwn.auth.common.crud.BaseManager;
import me.lwn.auth.common.crud.BaseRepository;
import me.lwn.auth.domain.Client;
import me.lwn.auth.infrastructure.repository.ClientRepository;
import me.lwn.auth.resources.cmd.RegisteredClientCommand;
import me.lwn.auth.resources.vo.RegisteredClientVo;
import me.lwn.auth.security.oauth2.server.authorization.ServerSetting;
import me.lwn.auth.security.oauth2.server.authorization.ServerSettingRepository;
import me.lwn.auth.security.utils.Constants;
import me.lwn.auth.utils.DateUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.*;

@Service
public class RegisteredClientManager extends BaseManager<Client, String> {

    private final RegisteredClientRepository repository;
    private final ServerSettingRepository serverSettingRepository;
    private final ClientRepository clientRepository;

    public RegisteredClientManager(RegisteredClientRepository repository, ServerSettingRepository serverSettingRepository,
                                   ClientRepository clientRepository) {
        this.repository = repository;
        this.serverSettingRepository = serverSettingRepository;
        this.clientRepository = clientRepository;
    }

    /**
     * TODO 此处自定义了一些配置项如：
     * {@link me.lwn.auth.security.utils.Constants.ClientSetting}
     * <p>
     * {@link me.lwn.auth.security.utils.Constants.TokenSetting}
     * <p>
     * 自定义的配置项在client创建时默认值可以使用全局配置oauth_server_setting中的配置,
     * 判断参数是否为全局参数，为全局则直接取oauth_server_setting中的值
     */
    public RegisteredClientVo save(RegisteredClientCommand cmd) {
        ServerSetting serverSetting = serverSettingRepository.get();
        TokenSettings tokenSettings;
        ClientSettings clientSettings;
        if (serverSetting.isGlobalTokenSetting()) {
            tokenSettings = tokenSettings(serverSetting);
        } else {
            tokenSettings = TokenSettings.builder().settings(settings -> cmd.getTokenSettings().forEach((k, v) -> {
                if (k.contains("time_to_live")) {
                    settings.put(k, Duration.ofSeconds((Long) v));
                } else {
                    settings.put(k, v);
                }
            })).build();
        }
        if (serverSetting.isGlobalClientSetting()) {
            clientSettings = clientSettings(serverSetting);
        } else {
            clientSettings = ClientSettings.builder().settings(settings -> settings.putAll(cmd.getClientSettings())).build();
        }
        String id = cmd.getId();
        if (!StringUtils.hasText(id)) {
            id = UUID.randomUUID().toString();
        }

        RegisteredClient.Builder builder = RegisteredClient.withId(id)
                .clientId(cmd.getClientId())
                .clientSecret(cmd.getClientSecret())
                .clientName(cmd.getClientName())
                .clientIdIssuedAt(DateUtils.localDateToInstant(cmd.getClientIdIssuedAt()))
                .clientSecretExpiresAt(DateUtils.localDateToInstant(cmd.getClientSecretExpiresAt()));

        builder.clientAuthenticationMethods(clientAuthenticationMethods ->
                cmd.getClientAuthenticationMethods().forEach(method ->
                        clientAuthenticationMethods.add(new ClientAuthenticationMethod(method))));

        builder.authorizationGrantTypes(authorizationGrantTypes ->
                cmd.getAuthorizationGrantTypes().forEach(grantType ->
                        authorizationGrantTypes.add(new AuthorizationGrantType(grantType))));

        builder.scopes(scopes -> scopes.addAll(cmd.getScopes()));
        builder.redirectUris(redirectUris -> redirectUris.addAll(cmd.getRedirectUris()));
        RegisteredClient client = builder.clientSettings(clientSettings).tokenSettings(tokenSettings).build();
        repository.save(client);
        return transfer(client);
    }

    public RegisteredClientVo init() {
        ServerSetting serverSetting = serverSettingRepository.get();

        RegisteredClientVo vo = new RegisteredClientVo();
        vo.setClientId(UUID.randomUUID().toString().replace("-", ""));
        vo.setClientSecret(RandomStringUtils.randomAlphanumeric(8));
        vo.setGlobalClientSettings(serverSetting.isGlobalClientSetting());
        vo.setClientSettings(clientSettings(serverSetting).getSettings());
        vo.setGlobalTokenSettings(serverSetting.isGlobalTokenSetting());
        vo.setTokenSettings(toTokenSettingsShow(tokenSettings(serverSetting)));
        return vo;
    }

    public RegisteredClientVo get(String id) {
        RegisteredClient client = repository.findById(id);
        if (client == null) {
            throw new RuntimeException("");
        }
        return transfer(client);
    }

    private RegisteredClientVo transfer(RegisteredClient client) {
        ServerSetting serverSetting = serverSettingRepository.get();
        RegisteredClientVo vo = new RegisteredClientVo();
        vo.setId(client.getId());
        vo.setClientId(client.getClientId());
        if (client.getClientIdIssuedAt() != null) {
            vo.setClientIdIssuedAt(DateUtils.instantToLocalDate(client.getClientIdIssuedAt()));
        }
        if (client.getClientSecretExpiresAt() != null) {
            vo.setClientSecretExpiresAt(DateUtils.instantToLocalDate(client.getClientSecretExpiresAt()));
        }
        vo.setClientName(client.getClientName());
        vo.setClientAuthenticationMethods(clientAuthenticationMethods(client.getClientAuthenticationMethods()));
        vo.setAuthorizationGrantTypes(authorizationGrantTypes(client.getAuthorizationGrantTypes()));
        vo.setRedirectUris(client.getRedirectUris());
        vo.setScopes(client.getScopes());
        vo.setGlobalClientSettings(serverSetting.isGlobalClientSetting());
        vo.setClientSettings(client.getClientSettings().getSettings());
        vo.setGlobalTokenSettings(serverSetting.isGlobalTokenSetting());
        vo.setTokenSettings(toTokenSettingsShow(client.getTokenSettings()));
        return vo;
    }

    public void delete(String id) {
        clientRepository.deleteById(id);
    }

    private Set<String> clientAuthenticationMethods(Set<ClientAuthenticationMethod> methods) {
        Set<String> set = new HashSet<>();
        methods.forEach(method -> set.add(method.getValue()));
        return set;
    }

    private Set<String> authorizationGrantTypes(Set<AuthorizationGrantType> methods) {
        Set<String> set = new HashSet<>();
        methods.forEach(method -> set.add(method.getValue()));
        return set;
    }

    private Map<String, Object> toTokenSettingsShow(TokenSettings tokenSettings) {
        Map<String, Object> res = new HashMap<>();
        tokenSettings.getSettings().forEach((k, v) -> {
            if (v instanceof Duration) {
                res.put(k, ((Duration) v).getSeconds());
            } else {
                res.put(k, v);
            }
        });
        return res;
    }

    private TokenSettings tokenSettings(ServerSetting serverSetting) {
        return TokenSettings.builder()
                .accessTokenTimeToLive(Duration.ofSeconds(serverSetting.getAccessTokenTimeToLive()))
                .refreshTokenTimeToLive(Duration.ofSeconds(serverSetting.getRefreshTokenTimeToLive()))
                .reuseRefreshTokens(serverSetting.isReuseRefreshTokens())
                .setting(Constants.TokenSetting.STATE_TIME_TO_LIVE.getKey(), Duration.ofSeconds(serverSetting.getStateTimeToLive()))
                .build();
    }

    private ClientSettings clientSettings(ServerSetting serverSetting) {

        ClientSettings.Builder clientBuilder = ClientSettings.builder()
                .requireProofKey(serverSetting.isRequireProofKey())
                .requireAuthorizationConsent(serverSetting.isRequireAuthorizationConsent())
                .setting(Constants.ClientSetting.USER_LOGIN_FAIL_LOCK_TIMES.getKey(), serverSetting.getUserLoginFailLockTimes())
                .setting(Constants.ClientSetting.LOGIN_LOCKED_DURATION.getKey(), serverSetting.getLoginFailLockedDuration())
                .setting(Constants.ClientSetting.IP_LOGIN_FAIL_LOCKED.getKey(), serverSetting.isIpLoginFailLocked())
                .setting(Constants.ClientSetting.REQUIRE_CAPTCHA.getKey(), serverSetting.isRequireCaptcha())
                .setting(Constants.ClientSetting.CUSTOMIZE_LOGIN_PAGE.getKey(), serverSetting.isCustomizeLoginPage());

        if (StringUtils.hasText(serverSetting.getTokenEndpointAuthenticationSigningAlgorithm())) {
            clientBuilder.tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.from(serverSetting.getTokenEndpointAuthenticationSigningAlgorithm()))
                    .jwkSetUrl(serverSetting.getJwkSetUrl());
        }
        return clientBuilder.build();
    }

    @Override
    protected BaseRepository<Client, String> getRepository() {
        return clientRepository;
    }
}
